package devcken.net.socket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;

import devcken.text.Encoding;
import devcken.utility.BitConverter;
import devcken.utility.ByteUtils;

/**
 * 소켓 리스너의 accept에 의해 소켓 클라이언트가 접속된 후 전달되는 첫 번째 데이터의 내용으로 소켓 클라이언트의 기본 정보를 나타낸다.
 * 이러한 기본 정보를 Accept 조건이라고 하며, AcceptCondition은 이를 위한 분석과 변환을 지원한다. 
 * @author Devcken
 */
public class AcceptCondition
{
	/**
	 * ACCEPT 패킷의 헤더 상수를 나타낸다.
	 */
	private final static byte[] _ACCEPT_HEADER = new byte[] { (byte)255, (byte)255, (byte)255 };

	/**
	 * 소켓 아이디의 길이 상수를 나타낸다.
	 */
	private final static int _ID_LENGTH = 36;
	
	/**
	 * 소켓의 고유 코드 정보를 나타낸다.
	 */
	private String _id;
	
	/**
	 * 메모리에 배열을 정렬하는 방식을 가져온다.
	 */
	private ByteOrder _endian;
	
	AcceptCondition(String id, ByteOrder endian)
	{
		this._id = id;
		this._endian = endian;
	}
	
	/**
	 * byte 배열을 분석하여 Accept 조건을 추출한다.
	 * @param data Accept 조건이 변환된 byte 배열을 설정한다.
	 * @param condition 변환된 Accept 조건을 참조한다.
	 * @return byte 배열을 분석하여 정상적으로 Accept 조건을 추출하면 true, 그렇지 않으면 false가 반환된다.
	 */
	static boolean parse(byte[] data, AcceptCondition condition)
	{
		try
		{
			int offset = 0;
			
			if (ByteUtils.indexOf(data, _ACCEPT_HEADER) < 0)
			{
				condition = null;
				
				return false;
			}
			
			offset += _ACCEPT_HEADER.length;
			
			String idString = Encoding.UTF8.getString(data, 0, _ID_LENGTH);
			
			offset += _ID_LENGTH;
			
			ByteOrder endian = data[offset] == 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
			
			condition = new AcceptCondition(idString, endian);
			
			return true;
		}
		catch (Exception error)
		{
			throw error;
		}
	}
	
	/**
	 * Accept 조건을 byte 배열로 변환한다.
	 * @return Accept 조건이 byte 배열로 변환되어 반환된다.
	 * @throws IOException 스트림 관련 오류로 인해 발생할 수 있다.
	 */
	byte[] toByteArray() throws IOException
	{
		byte[] result = null;
		
		try(ByteArrayOutputStream stream = new ByteArrayOutputStream())
		{
			stream.write(_ACCEPT_HEADER);
			
			byte[] idBinary = BitConverter.getBytes(this._id);
			
			stream.write(idBinary);
			
			stream.write(new byte[] { (byte)(this._endian == ByteOrder.BIG_ENDIAN ? 0 : 1) });
			
			result = stream.toByteArray();
		}
		catch (IOException error)
		{
			throw error;
		}
		
		return result;
	}

	public String getId()
	{
		return this._id;
	}

	public ByteOrder getEndian()
	{
		return this._endian;
	}
}